﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using CommunicatorAPI;
using System.Text;
using System.Runtime.InteropServices;

namespace Presence_Art_Screen_Saver
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        Random _random;
        private List<Particle> _particles = null;
        private double _minDist = 200;
        private double _spring = .00001;
        private int _renderHeight;
        private int _renderWidth;
        Random _r;
        CommunicatorAPI.Messenger communicator;

        /// <summary>
        /// Initializes a new instance of the <see cref="Window1"/> class.
        /// </summary>
        /// <param name="height">The height.</param>
        /// <param name="width">The width.</param>
        public Window1(int height, int width)
        {
            InitializeComponent();

            // Create a Messenger object using the API
            try
            {
                communicator = new CommunicatorAPI.Messenger();

                // Register OnContactStatusChange and OnIMWindowCreated event handler
                communicator.OnContactStatusChange += new DMessengerEvents_OnContactStatusChangeEventHandler(OnContactStatusChange);
                communicator.OnIMWindowCreated += new DMessengerEvents_OnIMWindowCreatedEventHandler(communicator_OnIMWindowCreated);

                _renderWidth = width;
                _renderHeight = height;

                AddParticles();

                CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);
            }
            catch (Exception)
            {
                this.lblNoConnection.Visibility = Visibility.Visible;
            }

#if !DEBUG

            MouseMove += new MouseEventHandler(Window2_MouseMove);
            MouseDown += new MouseButtonEventHandler(Window2_MouseDown);
            KeyDown += new KeyEventHandler(Window2_KeyDown);
#endif

        }


        /// <summary>
        /// Handles the window created event in the OCS client.
        /// </summary>
        /// <param name="pIMWindow"></param>
        void communicator_OnIMWindowCreated(object pIMWindow)
        {
            try
            {
                CommunicatorAPI.IMessengerConversationWndAdvanced stpIMWindow = pIMWindow as CommunicatorAPI.IMessengerConversationWndAdvanced;
                long Hwnd = (long)stpIMWindow.HWND;

                CommunicatorAPI.IMessengerContacts contactList = (CommunicatorAPI.IMessengerContacts)stpIMWindow.Contacts;

                foreach (CommunicatorAPI.IMessengerContact imc in contactList)
                {
                    Particle p = _particles.Find(a => a.DisplayName.Equals(imc.FriendlyName));

                    SetParticleAlert(p);
                }
            }
            catch
            {
                // Eat exceptions, not very nice, but better to still show the screensaver instead of just poping up exceptions
            }
        }

        private delegate void SetParticleAlertDelegate(Particle p);
        /// <summary>
        /// Set the alert mode for the particle
        /// </summary>
        /// <param name="particle"></param>
        private void SetParticleAlert(Particle particle)
        {
            if (this.Dispatcher.Thread != System.Threading.Thread.CurrentThread)
            {
                Dispatcher.Invoke(DispatcherPriority.Send, new SetParticleAlertDelegate(SetParticleAlert), particle);
                return;

            }
            else
            {
                particle.HasAlert = true;
                particle.Opacity = 1;
            }
        }

        /// <summary>
        /// Handles the status change event
        /// </summary>
        /// <param name="pMContact"></param>
        /// <param name="mStatus"></param>
        void OnContactStatusChange(object pMContact, MISTATUS mStatus)
        {
            IMessengerContact contact = pMContact as IMessengerContact;

            Particle p = _particles.Find(a => a.DisplayName.Equals(contact.FriendlyName));
            if (p != null)
            {
                ChangeParticle(p, contact);
            }
        }

        private delegate void ChangeParticleDelegate(Particle particle, IMessengerContact contact);
        /// <summary>
        /// A contact has a new status, so determine the icon and size/opacity
        /// </summary>
        /// <param name="particle"></param>
        /// <param name="contact"></param>
        private void ChangeParticle(Particle particle, IMessengerContact contact)
        {
            if (this.Dispatcher.Thread != System.Threading.Thread.CurrentThread)
            {
                Dispatcher.Invoke(DispatcherPriority.Send, new ChangeParticleDelegate(ChangeParticle), particle, contact);
                return;
            }
            else
            {
                particle.Status = DetermineStatus(contact.Status);
                particle.Radius = DetermineRadius(contact.Status);
                particle.Opacity = particle.Radius * .02;
            }
        }

        /// <summary>
        /// Handles the Rendering event of the CompositionTarget control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        void CompositionTarget_Rendering(object sender, EventArgs e)
        {

            for (int i = 0; i < NumberOfParticals; i++)
            {
                _particles[i].X += _particles[i].VX;
                _particles[i].Y += _particles[i].VY;

                Particle p = _particles[i];

                if (p.X > this.Width)
                {
                    p.X = 0;
                }

                else if (p.X < 0)
                {
                    p.X = this.Width;
                }

                if (p.Y > this.Height)
                {
                    p.Y = 0;
                }
                else if (p.Y < 0)
                {
                    p.Y = this.Height;
                }
            }


            for (int i = 0; i < NumberOfParticals - 1; i++)
            {
                var partA = _particles[i];
                for (int j = i + 1; j < NumberOfParticals; j++)
                {
                    var partB = _particles[j];
                    Spring(partA, partB);

                }
            }
        }

        /// <summary>
        /// Gets the number of particals.
        /// </summary>
        /// <value>The number of particals.</value>
        private int NumberOfParticals
        {
            get
            {
                return _particles.Count;
            }
        }

       
        /// <summary>
        /// Adds the particles.
        /// </summary>
        void AddParticles()
        {
            _random = new Random();
            _particles = new List<Particle>();

            _r = new Random();

            Particle p = null;

            // Check if the communicator is online
            if (communicator.MyStatus != MISTATUS.MISTATUS_OFFLINE)
            {
                IMessengerContacts contacts = communicator.MyContacts as IMessengerContacts;
                foreach (IMessengerContactAdvanced imc in contacts)
                {
                    p = new Particle();

                    p.Status = DetermineStatus(imc.Status);
                    p.HasAlert = false;

                    p.Radius = DetermineRadius(imc.Status);
                    p.Mass = p.Radius * .01;
                    p.Opacity = p.Radius * .02;

                    p.DisplayName = imc.FriendlyName;

                    this.mainGrid.Children.Add(p);

                    Canvas.SetTop(p, _random.Next(_renderHeight));
                    Canvas.SetLeft(p, _random.Next(_renderWidth));

                    p.X = Canvas.GetLeft(p);
                    p.Y = Canvas.GetTop(p);

                    _particles.Add(p);
                }
            }
            else
            {
                this.lblNoConnection.Visibility = Visibility.Visible;
            }
        }

        /// <summary>
        /// Determines the radius.
        /// </summary>
        /// <param name="status">The status.</param>
        /// <returns></returns>
        private double DetermineRadius(MISTATUS status)
        {
            switch (status)
            {
                case MISTATUS.MISTATUS_ALLOW_URGENT_INTERRUPTIONS:
                    return 45;
                case MISTATUS.MISTATUS_AWAY:
                    return 55;
                case MISTATUS.MISTATUS_BE_RIGHT_BACK:
                    break;
                case MISTATUS.MISTATUS_BUSY:
                    return 35;
                case MISTATUS.MISTATUS_CUSTOM:
                    break;
                case MISTATUS.MISTATUS_DO_NOT_DISTURB:
                    return 55;
                case MISTATUS.MISTATUS_IDLE:
                    return 45;
                case MISTATUS.MISTATUS_INVISIBLE:
                    return 15;
                case MISTATUS.MISTATUS_IN_A_CONFERENCE:
                    return 45;
                case MISTATUS.MISTATUS_IN_A_MEETING:
                    return 45;
                case MISTATUS.MISTATUS_LOCAL_CONNECTING_TO_SERVER:
                    break;
                case MISTATUS.MISTATUS_LOCAL_DISCONNECTING_FROM_SERVER:
                    break;
                case MISTATUS.MISTATUS_LOCAL_FINDING_SERVER:
                    break;
                case MISTATUS.MISTATUS_LOCAL_SYNCHRONIZING_WITH_SERVER:
                    break;
                case MISTATUS.MISTATUS_MAY_BE_AVAILABLE:
                    return 40;
                case MISTATUS.MISTATUS_OFFLINE:
                    return 15;
                case MISTATUS.MISTATUS_ONLINE:
                    return 65d;
                case MISTATUS.MISTATUS_ON_THE_PHONE:
                    return 40d;
                case MISTATUS.MISTATUS_OUT_OF_OFFICE:
                    return 5d;
                case MISTATUS.MISTATUS_OUT_TO_LUNCH:
                    return 30d;
                case MISTATUS.MISTATUS_UNKNOWN:
                    break;
                default:
                    break;
            }

            return 15d;
        }

        /// <summary>
        /// Determines the status.
        /// </summary>
        /// <param name="status">The status.</param>
        /// <returns></returns>
        private static Status DetermineStatus(MISTATUS status)
        {
            switch (status)
            {
                case MISTATUS.MISTATUS_AWAY:
                case MISTATUS.MISTATUS_BE_RIGHT_BACK:
                    return Status.Away;
                case MISTATUS.MISTATUS_IDLE:
                case MISTATUS.MISTATUS_BUSY:
                    return Status.Busy;
                case MISTATUS.MISTATUS_CUSTOM:
                    break;
                case MISTATUS.MISTATUS_DO_NOT_DISTURB:
                    return Status.Dnd;
                case MISTATUS.MISTATUS_INVISIBLE:
                    return Status.Offline;
                case MISTATUS.MISTATUS_IN_A_CONFERENCE:
                case MISTATUS.MISTATUS_ALLOW_URGENT_INTERRUPTIONS:
                case MISTATUS.MISTATUS_IN_A_MEETING:
                    return Status.InMtg;
                case MISTATUS.MISTATUS_LOCAL_CONNECTING_TO_SERVER:
                    break;
                case MISTATUS.MISTATUS_LOCAL_DISCONNECTING_FROM_SERVER:
                    break;
                case MISTATUS.MISTATUS_LOCAL_FINDING_SERVER:
                    break;
                case MISTATUS.MISTATUS_LOCAL_SYNCHRONIZING_WITH_SERVER:
                    break;
                case MISTATUS.MISTATUS_MAY_BE_AVAILABLE:
                    break;
                case MISTATUS.MISTATUS_OFFLINE:
                    return Status.Offline;
                case MISTATUS.MISTATUS_ONLINE:
                    return Status.Available;
                case MISTATUS.MISTATUS_ON_THE_PHONE:
                    return Status.Busy;
                case MISTATUS.MISTATUS_OUT_OF_OFFICE:
                    return Status.Offline;
                case MISTATUS.MISTATUS_OUT_TO_LUNCH:
                    return Status.InMtg;
                case MISTATUS.MISTATUS_UNKNOWN:
                    break;
                default:
                    break;
            }
            return Status.Unknown;
        }

        /// <summary>
        /// Springs the particles around the screen.
        /// </summary>
        /// <param name="a">A.</param>
        /// <param name="b">The b.</param>
        void Spring(Particle a, Particle b)
        {
            double dx = b.X - a.X;
            double dy = b.Y - a.Y;

            double dist = Math.Sqrt(dx * dx + dy * dy);
            if (dist < _minDist)
            {
                double ax = dx * _spring;
                double ay = dy * _spring;

                a.VX += ax;
                a.VY += ay;

                b.VX -= ax;
                b.VY -= ay;

                Canvas.SetLeft(a, a.X);
                Canvas.SetTop(a, a.Y);

                Canvas.SetLeft(b, b.X);
                Canvas.SetTop(b, b.Y);

            }

            Canvas.SetLeft(a, a.X);
            Canvas.SetTop(a, a.Y);

            Canvas.SetLeft(b, b.X);
            Canvas.SetTop(b, b.Y);

        }

        void Window2_KeyDown(object sender, KeyEventArgs e)
        {
            Application.Current.Shutdown();
        }

        void Window2_MouseDown(object sender, MouseButtonEventArgs e)
        {
            Application.Current.Shutdown();
        }

        void Window2_MouseMove(object sender, MouseEventArgs e)
        {
            Point currentPosition = e.MouseDevice.GetPosition(this);
            // Set IsActive and MouseLocation only the first time this event is called.
            if (!isActive)
            {
                mousePosition = currentPosition;
                isActive = true;
            }
            else
            {
                // If the mouse has moved significantly since first call, close.
                if ((Math.Abs(mousePosition.X - currentPosition.X) > 10) ||
                    (Math.Abs(mousePosition.Y - currentPosition.Y) > 10))
                {
                    Application.Current.Shutdown();
                }
            }
        }

        bool isActive;
        Point mousePosition;
    }
}